package com.richard.library.http.request;

import android.text.TextUtils;

import com.alibaba.fastjson.TypeReference;
import com.richard.library.http.converter.JsonConverter;
import com.richard.library.http.dict.RequestType;
import com.richard.library.http.dict.ResponseCode;
import com.richard.library.http.exception.HttpException;
import com.richard.library.http.manager.RetrofitClient;
import com.richard.library.http.model.UploadFileModel;
import com.richard.library.http.request.upload.FileRequestBody;
import com.richard.library.http.request.upload.UploadFileProgressListener;
import com.richard.library.http.util.NetUtil;
import com.richard.library.http.util.ReadResponseBodyUtil;
import com.richard.library.http.util.Utils;

import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import fr.arnaudguyon.xmltojsonlib.JsonToXml;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * <pre>
 * Description : 网络请求服务
 * Author : admin-richard
 * Date : 2018/3/1 15:43
 * Changelog:
 * Version            Date            Author              Detail
 * ----------------------------------------------------------------------
 * 1.0         2018/3/1 15:43     admin-richard         new file.
 * </pre>
 */
public final class RequestService {

    private final RequestType requestType;//请求类型
    private final boolean isStreaming;//响应是否以流的形式返回
    private final boolean isConvertXml;//是否将请求参数转换为xml
    private final String url;//请求URL
    private final Map<String, Object> header;//请求头信息
    private final Map<String, Object> params;//请求参数
    private final String stringParams;//String类型请求参数
    private final List<UploadFileModel> files;//上传文件
    private final Long timeout;//请求超时时间
    private final TimeUnit timeUnit;//请求超时时间单位
    private final UploadFileProgressListener uploadFileProgressListener;
    private final boolean followRedirect;//是否允许重定向
    private final ProcessRequestEvent processRequestEvent;
    private final ProcessResponseEvent processResponseEvent;
    private final Call<ResponseBody> call;

    public RequestService(RequestBuilder builder) {
        this.requestType = builder.getRequestType();
        this.isStreaming = builder.isStreaming();
        this.isConvertXml = builder.isConvertXml();
        this.followRedirect = builder.isFollowRedirect();
        this.url = builder.getUrl();
        this.header = null == builder.getHeader() ? new HashMap<>() : builder.getHeader();
        this.params = builder.getParamsMap();
        this.stringParams = builder.getParamsStr();
        this.files = builder.getFiles();
        this.timeout = builder.getTimeout();
        this.timeUnit = builder.getTimeoutUnit();
        this.uploadFileProgressListener = builder.getUploadFileProgressListener();
        this.processRequestEvent = builder.getProcessRequestEvent();
        this.processResponseEvent = builder.getProcessResponseEvent();

        //生成请求体
        this.call = generatorRequest();
    }

    /**
     * 生成http请求
     */
    private Call<ResponseBody> generatorRequest() {
        Call<ResponseBody> result;

        if (isConvertXml) {
            switch (requestType) {
                case GET:
                case HEAD:
                case POST_FORM:
                case UPLOAD_FILE:
                    throw new IllegalArgumentException(String.format(
                            "%s requests cannot be converted to xml parameters", requestType.name()));
                default:
                    header.put("Content-Type", "application/xml; charset=utf-8");
            }
        }

        switch (requestType) {
            case GET:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doGetStreaming(url, header, this.getMapParams());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doGet(url, header, this.getMapParams());
                }
                break;
            case POST:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPostStreaming(url, header, this.invokeProcessRequest());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPost(url, header, this.invokeProcessRequest());
                }
                break;
            case POST_FORM:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPostFormStreaming(url, header, this.getMapParams());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPostForm(url, header, this.getMapParams());
                }
                break;
            case UPLOAD_FILE:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doUploadFileStreaming(
                                    url
                                    , header
                                    , this.getMapParams()
                                    , new FileRequestBody(filesToMultipartBody(files), uploadFileProgressListener)
                            );
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doUploadFile(
                                    url
                                    , header
                                    , this.getMapParams()
                                    , new FileRequestBody(filesToMultipartBody(files), uploadFileProgressListener)
                            );
                }
                break;
            case DELETE:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doDeleteStreaming(url, header, this.invokeProcessRequest());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doDelete(url, header, this.invokeProcessRequest());
                }
                break;
            case HEAD:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doHeadStreaming(url, header, this.getMapParams());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doHead(url, header, this.getMapParams());
                }
                break;
            case PATCH:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPatchStreaming(url, header, this.invokeProcessRequest());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPatch(url, header, this.invokeProcessRequest());
                }
                break;
            case PUT:
                if (isStreaming) {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPutStreaming(url, header, this.invokeProcessRequest());
                } else {
                    result = RetrofitClient
                            .getInstance()
                            .getBaseAPIService(followRedirect)
                            .doPut(url, header, this.invokeProcessRequest());
                }
                break;
            default:
                throw new HttpException(String.format("%s request type is not supported", requestType.name()));
        }

        if (timeout != null && timeUnit != null) {
            result.timeout().timeout(timeout, timeUnit);
        }

        return result;
    }

    /**
     * 获取网络请求对象
     */
    public Call<ResponseBody> getCall() {
        return call;
    }

    /**
     * 设置当前请求超时时间
     *
     * @param timeout 时间数
     * @param unit    时间数单位
     */
    public RequestService timeout(long timeout, TimeUnit unit) {
        call.timeout().timeout(timeout, unit);
        return this;
    }

    /**
     * 获取请求方式类型
     */
    public RequestType getRequestType() {
        return requestType;
    }

    /**
     * 获取请求地址
     */
    public String getUrl() {
        return url;
    }

    /**
     * 获取请求header
     */
    public Map<String, Object> getHeader() {
        return header;
    }

    /**
     * 获取请求参数
     */
    public Map<String, Object> getParams() {
        return params;
    }

    /**
     * 获取自定义结构的请求参数
     */
    public String getStringParams() {
        return stringParams;
    }

    /**
     * 获取请求上传的文件
     */
    public List<UploadFileModel> getFiles() {
        return files;
    }

    /**
     * 异步请求
     *
     * @param callback 请求回调
     */
    public void request(Callback<ResponseBody> callback) {
        call.enqueue(callback);
    }

    /**
     * 同步请求
     */
    public Response<ResponseBody> request() {
        try {
            return call.execute();
        } catch (SocketTimeoutException ex) {
            ex.printStackTrace();
            throw new HttpException(ResponseCode.REQUEST_TIME_OUT.getMessage(), ex);
        } catch (IOException ex) {
            ex.printStackTrace();
            if (!NetUtil.isConnected()) {
                throw new HttpException(ResponseCode.NET_ERROR.getMessage(), ex);
            }
            throw new HttpException("访问连接失败", ex);
        }
    }

    /**
     * 同步请求
     */
    public <T> T request(TypeReference<T> type) throws Throwable {
        if (processResponseEvent == null) {
            return ReadResponseBodyUtil.read(this.request(), type);
        }

        try {
            return processResponseEvent.process(this, ReadResponseBodyUtil.read(
                    this.request(), type), null);
        } catch (Throwable e) {
            return processResponseEvent.process(this, null, e);
        }
    }

    /**
     * 同步请求
     */
    public <T> T requestXML(TypeReference<T> type) throws Throwable {
        if (processResponseEvent == null) {
            return ReadResponseBodyUtil.read(this.request(), type, true);
        }

        try {
            return processResponseEvent.process(this, ReadResponseBodyUtil.read(
                    this.request(), type, true), null);
        } catch (Throwable e) {
            return processResponseEvent.process(this, null, e);
        }
    }

    /**
     * 获取map请求参数
     */
    private Map<String, Object> getMapParams() {
        Object result = this.invokeProcessRequest();
        if (result == null) {
            return null;
        }

        if (result instanceof Map) {
            return (Map<String, Object>) result;
        }

        return JsonConverter.toJSONObject(result);
    }

    /**
     * 预处理请求参数
     */
    private Object invokeProcessRequest() {
        Object reqObj = Utils.getOrDefault(stringParams, params);

        if (processRequestEvent == null) {
            if (isConvertXml) {
                return getXmlParams(reqObj);
            }
            return reqObj;
        }

        if (reqObj == null) {
            return processRequestEvent.process(this, null);
        }

        if (reqObj instanceof String) {
            return processRequestEvent.process(this,
                    isConvertXml ? getXmlParams(reqObj) : (String) reqObj);
        }

        return processRequestEvent.process(this,
                isConvertXml ? getXmlParams(reqObj) : JsonConverter.toJson(reqObj));
    }

    /**
     * 获取xml请求参数
     */
    private String getXmlParams(Object params) {
        if (params == null) {
            return null;
        }

        if (params instanceof String) {
            if (Utils.isJson((String) params)) {
                return new JsonToXml.Builder((String) params).build().toString();
            }
            return (String) params;
        }

        return new JsonToXml.Builder(JsonConverter.toJson(params)).build().toString();
    }


    /**
     * 把File对象转化成MultipartBody
     */
    private MultipartBody filesToMultipartBody(List<UploadFileModel> files) {
        MultipartBody.Builder builder = new MultipartBody.Builder();

        for (UploadFileModel uploadFileModel : files) {
            File localFile = new File(uploadFileModel.getFilePath());
            String fileName = localFile.getName();
            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);

            String mineType = uploadFileModel.getMineType();
            if (uploadFileModel.isImage()) {
                mineType = "image/".concat(suffix);
            } else if (uploadFileModel.isVideo()) {
                mineType = "video/".concat(suffix);
            }

            RequestBody requestBody = RequestBody.create(
                    MediaType.parse(TextUtils.isEmpty(suffix)
                            ? "*/*"
                            : mineType
                    )
                    , localFile
            );

            builder.addFormDataPart(
                    TextUtils.isEmpty(uploadFileModel.getName())
                            ? "file"
                            : uploadFileModel.getName()
                    , localFile.getName()
                    , requestBody
            );
        }

        builder.setType(MultipartBody.FORM);
        return builder.build();
    }
}
